package com.fragrance.util;

import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import javax.imageio.ImageIO;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.binarywang.utils.qrcode.BufferedImageLuminanceSource;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import sun.font.FontDesignMetrics;

@SuppressWarnings("restriction")
public class QrUtils {

	private static final int BLACK = 0xFF000000;

	private static final int WHITE = 0xFFFFFFFF;

	private final static Logger logger = LoggerFactory.getLogger(QrUtils.class);

	private static BufferedImage toBufferedImage(BitMatrix matrix) {
		int width = matrix.getWidth();
		int height = matrix.getHeight();
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
			}
		}
		image.flush();
		return image;
	}

	/**
	 * 解析二维码（QRCode）
	 * 
	 * @param imgPath
	 *            图片路径
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static String decoderQRCode(String imgPath) {
		// QRCode 二维码图片的文件
		File imageFile = new File(imgPath);
		BufferedImage bufImg = null;
		String content = null;
		try {
			bufImg = ImageIO.read(imageFile);
			MultiFormatReader formatReader = new MultiFormatReader();
			LuminanceSource source = new BufferedImageLuminanceSource(bufImg);
			Binarizer binarizer = new HybridBinarizer(source);
			BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
			Map hints = new HashMap();
			hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
			//复杂模式，开启PURE_BARCODE模式
			hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
			Result result = formatReader.decode(binaryBitmap, hints);
			content = result.getText();
		} catch (IOException e) {
			logger.error("发生异常-1", e);
		} catch (NotFoundException e) {
			logger.error("发生异常-2", e);
		}
		return content;
	}

	/**
	 * 解析二维码（QRCode）
	 * 
	 * @param input
	 *            输入流
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static String decoderQRCode(InputStream input) {
		BufferedImage bufImg = null;
		String content = null;
		try {
			bufImg = ImageIO.read(input);
			MultiFormatReader formatReader = new MultiFormatReader();
			LuminanceSource source = new BufferedImageLuminanceSource(bufImg);
			Binarizer binarizer = new HybridBinarizer(source);
			BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
			Map hints = new HashMap();
			hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
			//开启PURE_BARCODE模式。（这是解决我的方案，带图片LOGO的解码方案）
			//复杂模式，开启PURE_BARCODE模式
			hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
			Result result = formatReader.decode(binaryBitmap, hints);
			content = result.getText();
		} catch (IOException e) {
			logger.error("发生异常-1", e);
		} catch (NotFoundException e) {
			logger.error("发生异常-2", e);
		}
		return content;
	}

	/**
	 * 输出到文件
	 * 
	 * @param matrix
	 * @param format
	 * @param file
	 * @throws IOException
	 */
	private static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {
		BufferedImage image = toBufferedImage(matrix);
		if (!ImageIO.write(image, format, file)) {
			throw new IOException("Could not write an image of format " + format + " to " + file);
		}
	}

	/**
	 * 输出到输出流
	 * 
	 * @param matrix
	 * @param format
	 * @param stream
	 * @throws IOException
	 */
	private static void writeToStream(BitMatrix matrix, String format, OutputStream stream) throws IOException {
		if (stream == null) {
			throw new IOException("输出流不能为空");
		}
		BufferedImage image = toBufferedImage(matrix);
		if (!ImageIO.write(image, format, stream)) {
			throw new IOException("Could not write an image of format " + format);
		}
	}

	/**
	 * 输出到输出流
	 * 
	 * @param qrData
	 * @param width
	 * @param height
	 * @param out
	 * @throws Exception
	 */
	public static byte[] generateQrOfPng(String qrData, int width, int height) throws Exception {
		ByteArrayOutputStream stream = null;
		try {
			stream = new ByteArrayOutputStream();
			// 二维码的图片格式
			String format = "png";
			Map<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
			// 内容所使用编码
			hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
			MultiFormatWriter bitMatrixWriter = new MultiFormatWriter();
			BitMatrix bitMatrix = bitMatrixWriter.encode(qrData, BarcodeFormat.QR_CODE, width, height, hints);
			writeToStream(bitMatrix, format, stream);
			return stream.toByteArray();
		} catch (Exception e) {
			throw e;
		} finally {
			if (stream != null) {
				try {
					stream.close();
				} catch (Exception e) {
				}
			}
		}
	}

	/**
	 * 生成二维码(QRCode)图片的公共方法
	 * 
	 * @param content
	 *            存储内容
	 * @param imgType
	 *            图片类型
	 * @param size
	 *            二维码尺寸(宽和高一样)
	 * @return
	 */
	public static byte[] generateQr(String qrData, String imgType, int size) throws Exception {
		ByteArrayOutputStream out = null;
		try {
			Map<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
			// 内容所使用编码
			hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
			MultiFormatWriter bitMatrixWriter = new MultiFormatWriter();
			BitMatrix bitMatrix = bitMatrixWriter.encode(qrData, BarcodeFormat.QR_CODE, size, size, hints);
			BufferedImage image = toBufferedImage(bitMatrix);
			out = new ByteArrayOutputStream();
			// 生成二维码QRCode图片
			ImageIO.write(image, imgType, out);
			return out.toByteArray();
		} catch (Exception e) {
			logger.error("生成二维码发生异常", e);
			throw e;
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (Exception e) {
				}
			}
		}
	}

	/**
	 * 输出到文件
	 * 
	 * @param qrData
	 * @param width
	 * @param height
	 * @param outputFile
	 * @throws Exception
	 */
	public static String generateQr(String qrData, int width, int height, File outputFile) throws Exception {
		if (outputFile == null) {
			throw new IOException("outputFile不能为空");
		}
		try {
			// 二维码的图片格式
			String format = "png";
			Map<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
			// 内容所使用编码
			hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
			MultiFormatWriter bitMatrixWriter = new MultiFormatWriter();
			BitMatrix bitMatrix = bitMatrixWriter.encode(qrData, BarcodeFormat.QR_CODE, width, height, hints);
			QrUtils.writeToFile(bitMatrix, format, outputFile);
			return outputFile.getAbsolutePath();
		} catch (Exception e) {
			throw e;
		}
	}

	/**
	 * 根据模板输出图片
	 * 
	 * @param qrData
	 * @param qrWidth
	 * @param qrHeight
	 * @param qrX
	 * @param qrY
	 * @param templateImage
	 *            模板
	 * @param text
	 * @param textY
	 * @param fontSize
	 * @return
	 * @throws Exception
	 */
	public static byte[] generateQrOfPngWitTemplate(String qrData, int qrWidth, int qrHeight, int qrX, int qrY,
			String templateImage, String text, int textY, int fontSize) throws Exception {
		if (StringUtils.isEmpty(templateImage)) {
			throw new Exception("模板不能为空");
		}
		if (StringUtils.isEmpty(text)) {
			throw new Exception("text不能为空");
		}
		FileInputStream templateFile = null;
		ByteArrayOutputStream out = null;
		Graphics2D g = null;
		try {
			// 二维码的图片格式
			String format = "png";
			Map<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
			// 内容所使用编码
			hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
			MultiFormatWriter bitMatrixWriter = new MultiFormatWriter();
			BitMatrix bitMatrix = bitMatrixWriter.encode(qrData, BarcodeFormat.QR_CODE, qrWidth, qrHeight, hints);
			templateFile = new FileInputStream(templateImage);
			BufferedImage bTemplateImage = ImageIO.read(templateFile);
			BufferedImage bimage = toBufferedImage(bitMatrix);
			g = bTemplateImage.createGraphics();
			g.drawImage(bimage, qrX, qrY, qrWidth, qrHeight, null);
			Font font = new Font("微软雅黑", Font.PLAIN, fontSize);
			FontMetrics fm = FontDesignMetrics.getMetrics(font);
			int strWidth = fm.stringWidth(text);
			int srtHeight = fm.getHeight();
			int bTemplateWith = bTemplateImage.getWidth();
			int textX = 0;
			g.setFont(font);
			if (bTemplateWith - strWidth >= 0) {
				textX = (bTemplateWith - strWidth) / 2;
				g.drawString(text, textX, textY);
			} else {
				// 说明超过一行
				// 在该字体下一个中文字符的宽度(中文字最宽) 随便用一个中文
				int oneLetterWith = fm.stringWidth("瑞");
				// 一行能写多少个字
				int count = bTemplateWith / oneLetterWith;
				// 有多少行字需要些
				int lines = text.length() % count == 0 ? text.length() / count : text.length() / count + 1;
				for (int i = 0; i < lines; i++) {
					int tempTextY = textY + (i * srtHeight);
					String s = null;
					if (i != lines - 1) {
						s = text.substring(i * count, (i + 1) * count);
					} else {
						s = text.substring(i * count);
					}
					strWidth = fm.stringWidth(s);
					textX = (bTemplateWith - strWidth) / 2;
					g.drawString(s, textX, tempTextY);
				}
			}
			bTemplateImage.flush();
			bimage.flush();
			out = new ByteArrayOutputStream();
			ImageIO.write(bTemplateImage, format, out);
			return out.toByteArray();
		} catch (Exception e) {
			throw e;
		} finally {
			if (templateFile != null) {
				try {
					templateFile.close();
				} catch (Exception e) {
				}
			}
			if (out != null) {
				try {
					out.close();
				} catch (Exception e) {
				}
			}
			if (g != null) {
				g.dispose();
			}
		}
	}

	// LOGO宽度
	private static final int LOGO_WIDTH = 80;
	
	// LOGO高度
	private static final int LOGO_HEIGHT = 80;

	/**
	 * 插入LOGO
	 * 
	 * @param source
	 *            二维码图片
	 * @param logoPath
	 *            LOGO图片地址
	 * @param needCompress
	 *            是否压缩
	 * @throws Exception
	 */
	private static void insertLogo(BufferedImage source, String logoPath, boolean needCompress, int qrWidth, int qrHeight)
			throws Exception {
		File file = new File(logoPath);
		if (!file.exists()) {
			logger.info(logoPath + "   该文件不存在！");
			return;
		}
		Image src = ImageIO.read(new File(logoPath));
		int width = src.getWidth(null);
		int height = src.getHeight(null);
		if (needCompress) { // 压缩LOGO
			if (width > LOGO_WIDTH) {
				width = LOGO_WIDTH;
			}
			if (height > LOGO_HEIGHT) {
				height = LOGO_HEIGHT;
			}
			Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
			BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			Graphics g = tag.getGraphics();
			g.drawImage(image, 0, 0, null); // 绘制缩小后的图
			g.dispose();
			src = image;
		}
		// 插入LOGO
		Graphics2D graph = source.createGraphics();
		int x = (qrWidth - width) / 2;
		int y = (qrHeight - height) / 2;
		graph.drawImage(src, x, y, width, height, null);
		Shape shape = new RoundRectangle2D.Float(x, y, width, width, 10, 10);
		graph.setStroke(new BasicStroke(3f));
		graph.draw(shape);
		graph.dispose();
	}

	private static BufferedImage createImage(String qrData, String logoPath, int qrWidth, int qrHeight, boolean needCompress)
			throws Exception {
		Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
		hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
		hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
		hints.put(EncodeHintType.MARGIN, 1);
		BitMatrix bitMatrix = new MultiFormatWriter().encode(qrData, BarcodeFormat.QR_CODE, qrWidth, qrHeight, hints);
		int width = bitMatrix.getWidth();
		int height = bitMatrix.getHeight();
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
			}
		}
		if (StringUtils.isBlank(logoPath)) {
			return image;
		}
		// 插入图片
		insertLogo(image, logoPath, needCompress, width, height);
		return image;
	}

	/**
	 * @param qrData
	 *            内容
	 * @param logoPath
	 *            LOGO地址
	 * @param size
	 * @param imageType
	 * @param needCompress
	 *            是否压缩LOGO
	 * @throws Exception
	 */
	public static byte[] generateQrWithLogo(String qrData, String logoPath, int qrWidth, int qrHeight, String imageType,
			boolean needCompress) {
		ByteArrayOutputStream out = null;
		try {
			out = new ByteArrayOutputStream();
			BufferedImage image = createImage(qrData, logoPath, qrWidth, qrHeight, needCompress);
			ImageIO.write(image, imageType, out);
			return out.toByteArray();
		} catch (Exception e) {
			logger.error("生成二维码失败", e);
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {
				}
			}
		}
		return null;
	}

	public static void main(String[] args) {
		try {
			String content = "1234567890";
			generateQr(content, 450, 450, new File("d:\\a.png"));
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

}
